Программирование сетевых приложений

Структурные элементы класса, методы взаимодействия объектов и организация наследования

Программирование сетевых приложений

Содержание лекции

  • Компонентные характеристики в определении класса
  • Доступ к переменным и методам
  • Конструкторы и создание объектов класса
  • Время жизни объекта и управление ресурсами (RAII)
  • Перегрузка и переопределение методов
  • Наследование классов
  • Создание многоуровневой иерархии
  • Динамическая диспетчеризация методов
  • Абстрактные классы и интерфейсы
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Компонентные характеристики класса

#include <string>
#include <ctime>

class NetworkDevice {
private:                    // Закрытые члены — детали реализации
    std::string macAddress;
    std::string ipAddress;
    bool isConnected;
    
protected:                  // Защищённые — доступны наследникам
    int signalStrength;
    
public:                     // Открытые — публичный интерфейс
    NetworkDevice();        // Конструктор по умолчанию
    NetworkDevice(const std::string& mac);  // Параметризованный
    
    void connect();
    void disconnect();
    bool checkStatus() const;  // const — не изменяет объект
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Спецификаторы доступа

class ServerConnection {
private:
    // Доступны только внутри класса (и friend)
    std::string password;
    void encryptData(std::string& data);
    
protected:
    // Доступны внутри класса и его наследникам
    std::string serverAddress;
    int port;
    void logConnection();
    
public:
    // Доступны из любого места программы
    void connect(const std::string& addr, int p);
    void sendData(const std::string& data);
    void disconnect();
};

// friend — полный доступ к private/protected
class ConnectionTest {
    friend class TestRunner;  // TestRunner видит всё
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Доступ к переменным и методам

class NetworkPacket {
private:
    int packetId;
    std::string data;
    std::time_t timestamp;
    
public:
    // Геттеры — const, возвращают по значению или const&
    int getPacketId() const { return packetId; }
    const std::string& getData() const { return data; }
    
    // Сеттер — валидирует входные данные
    void setData(const std::string& newData) {
        if (newData.empty()) return;           // Пустые данные не допускаются
        if (newData.length() > 1500) return;   // MTU limit
        data = newData;
        timestamp = std::time(nullptr);
    }
    
    bool isValid() const {
        return !data.empty() && packetId > 0;
    }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Инкапсуляция данных

class SecureConnection {
private:
    std::string encryptionKey;
    bool connectionEstablished;
    
    std::string generateKey() {
        return "key_" + std::to_string(std::time(nullptr));
    }
    
    bool validateCertificate() { return true; }
    
public:
    void establishConnection() {
        encryptionKey = generateKey();
        connectionEstablished = validateCertificate();
    }
    
    void sendSecureMessage(const std::string& message) {
        if (!connectionEstablished) return;  // Предусловие
        std::string encrypted = encrypt(message, encryptionKey);
        // Отправка зашифрованного сообщения
    }
    
    // Запрещаем копирование — соединение уникально
    SecureConnection(const SecureConnection&) = delete;
    SecureConnection& operator=(const SecureConnection&) = delete;
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Конструкторы класса

class NetworkClient {
private:
    std::string serverIp;
    int port;
    bool autoReconnect;
    
public:
    // Конструктор по умолчанию
    NetworkClient() 
        : serverIp("127.0.0.1"), port(8080), autoReconnect(true) {}
    
    // Параметризованный конструктор
    NetworkClient(const std::string& ip, int p) 
        : serverIp(ip), port(p), autoReconnect(true) {}
    
    // Конструктор копирования — компилятор генерирует автоматически
    NetworkClient(const NetworkClient&) = default;
    
    // Конструктор перемещения (C++11)
    NetworkClient(NetworkClient&& other) noexcept
        : serverIp(std::move(other.serverIp))
        , port(other.port)
        , autoReconnect(other.autoReconnect) {
        other.port = 0;
        other.autoReconnect = false;
    }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Списки инициализации

#include <string>
#include <vector>
#include <iostream>

class ServerConfig {
private:
    const int maxConnections;     // const — только через список инициализации
    const int bufferSize;         // const — только через список инициализации
    std::string serverName;
    std::vector<std::string> allowedIPs;
    
public:
    // Порядок в списке инициализации должен совпадать с объявлением!
    ServerConfig(const std::string& name, int maxConn) 
        : maxConnections(maxConn),       // 1-е поле
          bufferSize(4096),              // 2-е поле
          serverName(name),              // 3-е поле
          allowedIPs({"127.0.0.1", "192.168.1.1"}) {  // 4-е поле
        std::cout << "Server " << serverName << " configured" << std::endl;
    }
    
    // Delegating constructor (C++11)
    ServerConfig() : ServerConfig("default", 100) {}
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Деструктор класса

class NetworkResource {
private:
    std::vector<int> buffer;        // Автоматическое управление
    int socketDescriptor;
    
public:
    NetworkResource(int size) 
        : buffer(size), socketDescriptor(socket(AF_INET, SOCK_STREAM, 0)) {}
    
    ~NetworkResource() {
        if (socketDescriptor >= 0) {
            close(socketDescriptor);
        }
    }
    
    // Rule of Five: определяем все 5 специальных функций
    NetworkResource(const NetworkResource&) = delete;
    NetworkResource& operator=(const NetworkResource&) = delete;
    NetworkResource(NetworkResource&& other) noexcept
        : buffer(std::move(other.buffer))
        , socketDescriptor(other.socketDescriptor) {
        other.socketDescriptor = -1;  // Предотвращаем double-close
    }
    NetworkResource& operator=(NetworkResource&& other) noexcept {
        if (this != &other) {
            close(socketDescriptor);
            buffer = std::move(other.buffer);
            socketDescriptor = other.socketDescriptor;
            other.socketDescriptor = -1;
        }
        return *this;
    }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Правило Zero/Three/Five

// Rule of Zero — лучший вариант: класс НЕ управляет ресурсами напрямую
class NetworkConfig {
    std::string host;
    int port;
    bool useTLS;
public:
    NetworkConfig(const std::string& h, int p, bool tls = false)
        : host(h), port(p), useTLS(tls) {}
    // Компилятор автоматически генерирует корректные:
    // ~NetworkConfig(), copy/move ctor, copy/move assign
};

// Rule of Five — класс ВЛАДЕЕТ ресурсом
class SocketGuard {
    int fd_;
public:
    explicit SocketGuard(int fd) : fd_(fd) {}
    ~SocketGuard() { if (fd_ >= 0) close(fd_); }
    SocketGuard(const SocketGuard&) = delete;
    SocketGuard& operator=(const SocketGuard&) = delete;
    SocketGuard(SocketGuard&& o) noexcept : fd_(o.fd_) { o.fd_ = -1; }
    SocketGuard& operator=(SocketGuard&& o) noexcept {
        if (this != &o) { if (fd_ >= 0) close(fd_); fd_ = o.fd_; o.fd_ = -1; }
        return *this;
    }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Создание объектов класса

class NetworkService {
    std::string serviceName;
    int port;
public:
    NetworkService(const std::string& name, int p) : serviceName(name), port(p) {}
    void start() { /* ... */ }
};

int main() {
    // Стек — предпочтительно (автоматическое уничтожение)
    NetworkService httpService("HTTP", 80);
    httpService.start();
    
    // Куча через unique_ptr — когда размер/время жизни неизвестны
    auto ftpService = std::make_unique<NetworkService>("FTP", 21);
    ftpService->start();
    // delete не нужен — unique_ptr освободит автоматически
    
    // Массив/вектор объектов
    std::vector<NetworkService> services;
    services.emplace_back("SSH", 22);   // Конструирование на месте
    services.emplace_back("DNS", 53);   // Без лишних копирований
}
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Время жизни объекта

class ConnectionManager {
    static int activeConnections;
    int connectionId;
public:
    ConnectionManager() : connectionId(++activeConnections) {
        std::cout << "Connection " << connectionId << " created\n";
    }
    ~ConnectionManager() {
        activeConnections--;
        std::cout << "Connection " << connectionId << " destroyed\n";
    }
    static int getActiveConnections() { return activeConnections; }
    
    // Запрещаем копирование — соединение уникально
    ConnectionManager(const ConnectionManager&) = delete;
    ConnectionManager& operator=(const ConnectionManager&) = delete;
};

int ConnectionManager::activeConnections = 0;

void createTemporaryConnection() {
    ConnectionManager temp;
    std::cout << "Active: " << ConnectionManager::getActiveConnections() << "\n";
} // temp автоматически уничтожается — RAII в действии
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Перегрузка методов

class DataProcessor {
public:
    // Перегрузка по количеству параметров
    void processData(const std::string& data) {
        std::cout << "Single: " << data << std::endl;
    }
    void processData(const std::string& data1, const std::string& data2) {
        std::cout << "Pair: " << data1 << " and " << data2 << std::endl;
    }
    
    // Перегрузка по типам параметров
    void processData(int number) {
        std::cout << "Number: " << number << std::endl;
    }
    
    // Перегрузка по const — для const и non-const объектов
    std::string& data() { return cachedData_; }
    const std::string& data() const { return cachedData_; }
    
private:
    std::string cachedData_;
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Перегрузка операторов

class NetworkAddress {
    std::string ip_;
    int port_;
public:
    NetworkAddress(const std::string& ip, int p) : ip_(ip), port_(p) {}
    
    bool operator==(const NetworkAddress& other) const {
        return ip_ == other.ip_ && port_ == other.port_;
    }
    bool operator!=(const NetworkAddress& other) const {
        return !(*this == other);  // Используем == для !=
    }
    bool operator<(const NetworkAddress& other) const {
        return ip_ < other.ip_ || (ip_ == other.ip_ && port_ < other.port_);
    }
    
    // operator<< для вывода — через friend (нужен доступ к private)
    friend std::ostream& operator<<(std::ostream& os, const NetworkAddress& a) {
        return os << a.ip_ << ":" << a.port_;
    }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Переопределение методов (Override)

class NetworkProtocol {
public:
    virtual void sendData(const std::string& data) {
        std::cout << "Basic: " << data << std::endl;
    }
    virtual std::string receiveData() { return "Basic data"; }
    virtual ~NetworkProtocol() = default;
};

class TCPProtocol : public NetworkProtocol {
public:
    // override — проверка на этапе компиляции
    void sendData(const std::string& data) override {
        std::cout << "TCP reliable: " << data << std::endl;
    }
    std::string receiveData() override {
        return "TCP stream data";
    }
};

class UDPProtocol final : public NetworkProtocol {
public:
    void sendData(const std::string& data) override final {
        std::cout << "UDP datagram: " << data << std::endl;
    }
    std::string receiveData() override { return "UDP packet"; }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Виртуальные методы

class Connection {
protected:
    std::string connectionType;
public:
    Connection() : connectionType("Basic") {}
    
    virtual void establish() {
        std::cout << "Establishing " << connectionType << " connection\n";
    }
    
    // Чисто виртуальный метод — класс становится абстрактным
    virtual void transmit(const std::string& data) = 0;
    
    virtual ~Connection() = default;  // Виртуальный деструктор!
};

class SecureConnection : public Connection {
public:
    SecureConnection() { connectionType = "Secure"; }
    
    void establish() override {
        Connection::establish();  // Вызов базовой реализации
        std::cout << "TLS handshake completed\n";
    }
    
    void transmit(const std::string& data) override {
        std::cout << "Encrypted: " << data << std::endl;
    }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Наследование классов

class Device {
protected:
    std::string deviceId;
    std::string manufacturer;
    bool isActive;
public:
    Device(const std::string& id, const std::string& maker) 
        : deviceId(id), manufacturer(maker), isActive(false) {}
    virtual ~Device() = default;
    void activate() { isActive = true; }
    bool getStatus() const { return isActive; }
};

class NetworkDevice : public Device {
protected:
    std::string ipAddress;
public:
    NetworkDevice(const std::string& id, const std::string& maker, 
                  const std::string& ip) 
        : Device(id, maker), ipAddress(ip) {}  // Вызов конструктора базы
    std::string getIpAddress() const { return ipAddress; }
};

class Router final : public NetworkDevice {
    int portCount;
    std::vector<std::string> routingTable;
public:
    Router(const std::string& id, const std::string& maker, 
           const std::string& ip, int ports) 
        : NetworkDevice(id, maker, ip), portCount(ports) {}
    void addRoute(const std::string& dest) { routingTable.push_back(dest); }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Типы наследования

class BaseNetworkService {
public:
    void publicMethod() {}
protected:
    void protectedMethod() {}
private:
    void privateMethod() {}
};

// public — «IS-A» (стандартное наследование)
class PublicService : public BaseNetworkService {
    // publicMethod → public
    // protectedMethod → protected
};

// protected — редко используется
class ProtectedService : protected BaseNetworkService {
    // publicMethod → protected
    // protectedMethod → protected
};

// private — «implemented-in-terms-of»
class PrivateService : private BaseNetworkService {
    // publicMethod → private
    // protectedMethod → private
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Множественное наследование

class IConnectable {
public:
    virtual void connect() = 0;
    virtual void disconnect() = 0;
    virtual ~IConnectable() = default;
};

class IDataProcessor {
public:
    virtual void processData(const std::string& data) = 0;
    virtual ~IDataProcessor() = default;
};

// Реализация нескольких интерфейсов — безопасное множественное наследование
class SecureNetworkConnection : public IConnectable, public IDataProcessor {
    bool isConnected_ = false;
public:
    void connect() override { isConnected_ = true; }
    void disconnect() override { isConnected_ = false; }
    void processData(const std::string& data) override {
        if (isConnected_) { /* обработка */ }
    }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Проблема ромбовидного наследования

class NetworkComponent {
protected:
    std::string componentId;
public:
    NetworkComponent(const std::string& id) : componentId(id) {}
    virtual void initialize() = 0;
    virtual ~NetworkComponent() = default;
};

// virtual — гарантирует один экземпляр NetworkComponent
class Sender : virtual public NetworkComponent {
public:
    Sender(const std::string& id) : NetworkComponent(id) {}
    virtual void send(const std::string& data) = 0;
};

class Receiver : virtual public NetworkComponent {
public:
    Receiver(const std::string& id) : NetworkComponent(id) {}
    virtual std::string receive() = 0;
};

class Transceiver : public Sender, public Receiver {
public:
    Transceiver(const std::string& id) 
        : NetworkComponent(id), Sender(id), Receiver(id) {}
    // Теперь componentId — один экземпляр, без неоднозначности
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Сокрытие методов

class BaseServer {
public:
    void start() { std::cout << "Base server started\n"; }
    void stop()  { std::cout << "Base server stopped\n"; }
    virtual void handleRequest(const std::string& req) {
        std::cout << "Base: " << req << std::endl;
    }
};

class WebServer : public BaseServer {
public:
    // Сокрытие (hiding) — НЕ переопределение! start() не virtual
    void start() { 
        BaseServer::start();  // Явный вызов базового метода
        std::cout << "On port 80\n"; 
    }
    
    // Переопределение виртуального метода
    void handleRequest(const std::string& req) override {
        std::cout << "HTTP: " << req << std::endl;
    }
};

int main() {
    WebServer ws;
    ws.start();           // WebServer::start()
    ws.stop();            // BaseServer::stop() — не скрыт
    ws.BaseServer::start();  // Явный вызов скрытого метода
}
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Динамическая диспетчеризация

class NetworkHandler {
public:
    virtual void processPacket(const std::string& packet) {
        std::cout << "Basic processing\n";
    }
    virtual ~NetworkHandler() = default;
};

class TCPHandler : public NetworkHandler {
public:
    void processPacket(const std::string& packet) override {
        std::cout << "TCP: reliable delivery\n";
    }
};

// Полиморфизм через указатель на базовый класс
void handleTraffic(NetworkHandler& handler, const std::string& packet) {
    handler.processPacket(packet);  // Виртуальный вызов — runtime
}

int main() {
    TCPHandler tcp;
    UDPHandler udp;
    handleTraffic(tcp, "GET /");   // Вызов TCPHandler::processPacket
    handleTraffic(udp, "DATAGRAM"); // Вызов UDPHandler::processPacket
    // Нет new/delete — все объекты на стеке!
}
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Абстрактные классы

class Protocol {
protected:
    std::string protocolName;
    int defaultPort;
public:
    Protocol(const std::string& name, int port) 
        : protocolName(name), defaultPort(port) {}
    
    virtual void establishConnection(const std::string& addr, int port) = 0;
    virtual void sendData(const std::string& data) = 0;
    virtual std::string receiveData() = 0;
    virtual void closeConnection() = 0;
    
    virtual ~Protocol() = default;
    
    // Обычный метод — общая логика для всех наследников
    std::string getProtocolName() const { return protocolName; }
};

class HTTPProtocol : public Protocol {
public:
    HTTPProtocol() : Protocol("HTTP", 80) {}
    void establishConnection(const std::string& addr, int port) override { /*...*/ }
    void sendData(const std::string& data) override { /*...*/ }
    std::string receiveData() override { return "HTTP/1.1 200 OK\r\n\r\n"; }
    void closeConnection() override { /*...*/ }
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Интерфейсы в C++

class IEncryption {
public:
    virtual std::string encrypt(const std::string& plaintext) = 0;
    virtual std::string decrypt(const std::string& ciphertext) = 0;
    virtual ~IEncryption() = default;
};

class IAuthentication {
public:
    virtual bool authenticate(const std::string& user, const std::string& pass) = 0;
    virtual void logout() = 0;
    virtual ~IAuthentication() = default;
};

// Реализация нескольких интерфейсов
class SecureService final : public IEncryption, public IAuthentication {
    int logLevel_ = 0;
public:
    std::string encrypt(const std::string& p) override { return "enc_" + p; }
    std::string decrypt(const std::string& c) override { return c.substr(4); }
    bool authenticate(const std::string& u, const std::string& p) override {
        return u == "admin" && p == "secret";
    }
    void logout() override {}
};
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Шаблонные классы

template <typename T>
class NetworkBuffer {
    std::vector<T> buffer_;
    size_t maxSize_;
public:
    explicit NetworkBuffer(size_t size) : maxSize_(size) {}
    
    bool add(const T& item) {
        if (buffer_.size() >= maxSize_) return false;
        buffer_.push_back(item);
        return true;
    }
    
    std::optional<T> get() {  // C++17 — безопаснее чем T()
        if (buffer_.empty()) return std::nullopt;
        T item = std::move(buffer_.front());
        buffer_.erase(buffer_.begin());
        return item;
    }
    
    size_t size() const { return buffer_.size(); }
    bool empty() const { return buffer_.empty(); }
};

NetworkBuffer<std::string> stringBuffer(10);
stringBuffer.add("Hello");
auto val = stringBuffer.get();  // std::optional<string>
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Заключение

Основные концепции:

  • Инкапсуляция — сокрытие данных и реализации через private/protected
  • Наследование — повторное использование кода, отношение IS-A
  • Полиморфизм — единый интерфейс, разное поведение через virtual
  • Абстракция — выделение общих характеристик через абстрактные классы
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Рекомендации:

  1. Стремитесь к Rule of Zero — не управляйте ресурсами напрямую
  2. Предпочитайте композицию наследованию, когда это возможно
  3. Всегда используйте override и virtual деструктор
  4. Используйте = delete для запрета нежелательных операций
  5. Множественное наследование — только от интерфейсов
  6. Управляйте временем жизни через RAII и умные указатели
Структурные элементы класса, методы взаимодействия объектов и организация наследования
Программирование сетевых приложений

Вопросы для самопроверки

  1. Чем отличаются спецификаторы доступа private, protected и public?
  2. Какие типы конструкторов существуют в C++?
  3. Что такое Rule of Zero/Three/Five?
  4. В чём различие между перегрузкой и переопределением методов?
  5. Зачем нужен override и что произойдёт без него?
  6. Как решается проблема ромбовидного наследования?
  7. Что такое чисто виртуальные методы и абстрактные классы?
  8. Как работает динамическая диспетчеризация (vtable)?
Структурные элементы класса, методы взаимодействия объектов и организация наследования

Лекция посвящена ООП-механизмам C++ в контексте сетевых приложений. Студенты уже знакомы с базовыми конструкциями (лекция 03), теперь переходим к организации классов, наследованию и полиморфизму. Акцент: современные практики (Rule of Zero, override, smart pointers).

Порядок тем: от структуры класса к наследованию, затем полиморфизм. На лекцию ~90 минут. Время на вопросы — в конце.

struct vs class: единственное отличие — default access (public у struct, private у class). Семантически: struct — данные без поведения, class — инкапсуляция данных и методов. Рекомендация: все поля данных — private, доступ — через геттеры/сеттеры. protected используется осторожно — лучше через private + protected-методы.

friend нарушает инкапсуляцию — используйте только для тестов и operator<<. friend не наследуется и не транзитивен. Практика: делайте данные private, методы — public/protected. Золотое правило: минимальная видимость — лучший выбор.

Геттеры должны быть const — не изменяют объект. Возврат const& для строк избегает копирования. Сеттеры валидируют — инкапсуляция защищает данные. MTU (Maximum Transmission Unit) = 1500 байт для Ethernet — реальное ограничение в сети, используемое в примере.

= delete — явно запрещаем копирование. Соединение нельзя «размножить»: два объекта с одним сокетом/ключом — ошибка. Используйте = delete для запрета нежелательных операций. Альтернатива: наследование от boost::noncopyable.

Список инициализации (: member(value)) — ЕДИНСТВЕННЫЙ способ инициализировать const-поля, ссылки и поля без конструктора по умолчанию. Порядок инициализации — порядок объявления в классе, НЕ порядок в списке инициализации. Компилятор выдаёт warning если порядок не совпадает. move-конструктор «крадёт» ресурсы у исходного объекта — важен для std::unique_ptr, std::string, std::vector. noexcept — обещание не бросать исключения, позволяет оптимизации.

Делегирующий конструктор — вызывает другой конструктор того же класса. Избегает дублирования кода инициализации. Внимание: при делегировании тело делегирующего конструктора выполняется ПОСЛЕ вызванного конструктора. Нельзя одновременно делегировать и инициализировать поля в списке.

Rule of Five: если класс владеет ресурсом (сокет, файл, память), определите все 5: деструктор, copy ctor, copy assign, move ctor, move assign. В данном примере копирование запрещено (= delete), т.к. дублировать сокет нельзя. Перемещение разрешено — передаём владение. В идеале — оберните сокет в RAII-класс и используйте Rule of Zero.

Rule of Zero: если класс не владеет raw-ресурсами — не определяйте никаких специальных функций. Компилятор сгенерирует правильные. Rule of Five: если владеете (raw pointer, fd, HANDLE) — определите все пять. Нарушение этого правила — источник багов. Всегда стремитесь к Rule of Zero через RAII-обёртки. explicit предотвращает неявные преобразования: SocketGuard s = 5; — ошибка.

Стек — всегда предпочтительнее: нет аллокации, нет утечек, лучше кэш-локальность. make_unique — exception-safe, короче, единый стиль. emplace_back vs push_back: emplace_back конструирует объект прямо в контейнере, без промежуточного копирования/перемещения.

RAII = Resource Acquisition Is Initialization. Ресурс захватывается в конструкторе, освобождается в деструкторе. Гарантия: деструктор вызовется ДАЖЕ при исключении (stack unwinding). Это главное преимущество C++ перед Java/C# (нет finally — он не нужен).

Перегрузка (overloading) — несколько функций с ОДИНАКОВЫМ именем, но РАЗНЫМИ сигнатурами (типы/количество параметров). Возвращаемый тип НЕ участвует в разрешении перегрузки! const на методе — часть сигнатуры: const-объект вызывает const-версию. Нельзя перегрузить по умолчательным параметрам — это другая функция.

Перегружаемые операторы: +, -, *, /, ==, !=, <, >, <<, >>, [], (), -> НЕ перегружаемые: ., .*, ::, ?:, sizeof, typeid Правило: перегружайте операторы только если смысл очевиден. operator< нужен для использования в std::map, std::set, std::sort. В C++20 можно заменить все сравнения одним operator<=>

override — КРИТИЧЕСКИ важный атрибут. Без него опечатка в сигнатуре создаст НОВЫЙ метод вместо переопределения — баг без ошибки компиляции. final на классе: нельзя наследовать (class MyUDP : public UDPProtocol — ошибка). final на методе: нельзя переопределить в дальнейших наследниках. Практика: ВСЕГДА используйте override. Включите -Wsuggest-override в GCC.

Виртуальный деструктор — ОБЯЗАТЕЛЕН для полиморфных базовых классов! Без него: Base* ptr = new Derived(); delete ptr; — UB (деструктор Derived не вызовется, ресурсы утечкут). Чисто виртуальный (= 0) — метод без реализации, класс нельзя создать. Вызов базового метода: Connection::establish() — частый паттерн для расширения, а не замены поведения.

Порядок конструирования: Device → NetworkDevice → Router. Порядок уничтожения: Router → NetworkDevice → Device (обратный). Наследование — отношение «IS-A»: Router IS-A NetworkDevice IS-A Device. Если отношения IS-A нет — используйте композицию (HAS-A). final на Router — запрещает дальнейшее наследование.

public наследование — подавляющее большинство случаев (99%). private наследование — альтернатива композиции, когда нужен доступ к protected-членам базы или override виртуальных методов. protected наследование — почти никогда не используется. В Qt:几乎所有 классы используют public наследование от QObject. Рекомендация: если сомневаетесь — используйте public + композиция.

Множественное наследование интерфейсов (все методы = 0) — безопасно, нет проблемы ромба. Это рекомендуемый паттерн в C++. Множественное наследование КОНКРЕТНЫХ классов — опасно: проблема ромбовидного наследования, дублирование данных. Java/C# решают это через интерфейсы (один класс + много интерфейсов). В C++ «интерфейс» = класс только с чисто виртуальными методами.

Без virtual: Transceiver содержит ДВА экземпляра NetworkComponent — один через Sender, другой через Receiver. componentId неоднозначен! virtual наследование: один общий экземпляр NetworkComponent. Цена: дополнительный указатель (vptr) в каждом виртуальном базовом классе. Внимание: самый производный класс (Transceiver) ВСЕГДА вызывает конструктор виртуальной базы (NetworkComponent) напрямую. Рекомендация: избегайте ромбовидного наследования через интерфейсы.

Сокрытие (hiding): если наследник объявляет метод с тем же именем, ВСЕ перегрузки базового класса скрываются. Решение: using BaseServer::start; // в классе наследника — раскрывает все overload-ы Не путайте hiding и overriding: hiding — метод НЕ virtual, вызов определяется ТИПОМ указателя. overriding — метод virtual, вызов определяется ОБЪЕКТОМ (runtime).

vtable (virtual table) — скрытая таблица указателей на функции. Каждый объект с virtual-методами содержит vptr → указатель на vtable. Вызов virtual-метода: obj->vptr[method_index](args) — косвенный вызов. Overhead: одно дополнительное чтение из памяти (обычно незначительно). devirtualization: компилятор может оптимизировать, если тип известен на этапе компиляции. final помогает компилятору девиртуализировать.

Абстрактный класс = хотя бы один чисто виртуальный метод (= 0). Нельзя создать объект: Protocol p; — ошибка компиляции. Можно иметь указатель/ссылку: Protocol* p = new HTTPProtocol(); Обычные методы в абстрактном классе — общая логика (DRY-принцип). Чисто виртуальный метод МОЖЕТ иметь реализацию: virtual void foo() = 0; // в .h void Protocol::foo() { /* default impl */ } // в .cpp Наследник вызывает: Protocol::foo();

C++ не имеет ключевого слова interface (как Java/C#). Конвенция: имя с префиксом I (IEncryption), только чисто виртуальные методы + virtual деструктор. Это «чистый интерфейс». Преимущества: - Разделение «что делать» от «как делать» - Легко подменять реализацию (mock-объекты для тестов) - Dependency Injection через указатель на интерфейс В Qt: аналогичные паттерны используются в QIODevice, QAbstractSocket.

Шаблоны — обобщённое программирование. Код генерируется для каждого типа при компиляции (code bloat — потенциальная проблема). explicit на конструкторе предотвращает: NetworkBuffer buf = 10; std::optional<T> — безопасная альтернатива T() при пустом буфере. Позволяет вызывающей стороне проверить: if (auto v = buf.get()) { ... } В Qt: шаблоны используются реже, предпочитаются Qt-контейнеры.

Четыре столпа ООП, применённые к сетевым приложениям: - Инкапсуляция: защита ключей шифрования, соединений - Наследование: иерархия Device → NetworkDevice → Router - Полиморфизм: единый handler для TCP/UDP/HTTP - Абстракция: Protocol как база для всех протоколов

Главный принцип: пишите безопасный код. Компилятор — ваш друг, используйте override, = delete, final, explicit, const, noexcept для выражения намерений и раннего обнаружения ошибок.

Вопросы обновлены: добавлен вопрос о Rule of Zero/Three/Five (3), уточнён вопрос про override (5). Ответы: 3) Zero: класс без ресурсов — не определять спецфункции. Five: владеет ресурсом — определить все пять. 5) Без override опечатка в сигнатуре создаст новый метод — баг без ошибки компиляции. override превращает это в ошибку компиляции.